import {DbClient, DbConfig, Transaction, TxFun} from "./dbClient";
import {ITask} from "pg-promise";
import {TableConfig, ValidationError} from "../common";

const pgPromise = require("pg-promise");
export const pgp = pgPromise({
    capSQL: true,
    error(error: any, e: any) {
        if (e.cn) {
            console.error('Connection error:', error.message || error);
        }
        if (e.query) {
            console.error('Query error:', error.message || error);
            console.error('Query:', e.query);
        }
    }
});

// BIGINT | BIGSERIAL 转化
pgp.pg.types.setTypeParser(20, parseInt);
// decimal 转化
pgp.pg.types.setTypeParser(1700, parseFloat);

class PostgresqlTransaction extends Transaction {
    private t: ITask<any>;

    constructor(t: ITask<any>) {
        super();
        this.t = t;
    }

    async oneOrNone(sql: string): Promise<any> {
        return this.t.oneOrNone(sql);
    }

    async one(sql: string): Promise<any> {
        return this.t.one(sql);
    }

    async query(sql: string): Promise<any> {
        return this.t.query(sql);
    }

    async none(sql: string): Promise<void> {
        await this.t.none(sql);
    }
}

export class Postgresql extends DbClient {
    private client = pgp({});

    constructor({host, port, database, user, password}: DbConfig) {
        const config = {host, port, database, user, password};
        super(config);
        if (Object.values(config).filter(Boolean).length > 0) {
            this.client = pgp({
                host: this.host,
                port: this.port,
                database: this.database,
                user: this.user,
                password: this.password,
                max: 20,
                idleTimeoutMillis: 30000,
                connectionTimeoutMillis: 2000,
            });
        }
    }

    setConnection(connection: any): void {
        this.client = connection;
    }

    async connect(): Promise<void> {
        try {
            await this.client.connect();
            console.info(`Connected to postgres: ${this.host}`);
        } catch (e: any) {
            console.error('PostgreSQL connection error:', e);
            throw new Error(`Failed to connect to PostgreSQL: ${e.message}`);
        }
    }

    private getExecutor(transaction?: PostgresqlTransaction) {
        return transaction ? transaction : this.client;
    }

    async oneOrNone(sql: string, transaction?: PostgresqlTransaction): Promise<any> {
        return this.getExecutor(transaction).oneOrNone(sql);
    }

    async one(sql: string, transaction?: PostgresqlTransaction): Promise<any> {
        return this.getExecutor(transaction).one(sql);
    }

    async query(sql: string, transaction?: PostgresqlTransaction): Promise<any> {
        return this.getExecutor(transaction).query(sql);
    }

    async none(sql: string, transaction?: PostgresqlTransaction): Promise<void> {
        await this.getExecutor(transaction).none(sql);
    }

    async tx(func: TxFun): Promise<any> {
        return this.client.tx(async (t: ITask<any>) => {
            const transaction = new PostgresqlTransaction(t);
            return await func(transaction);
        });
    }

    insertSql(data: object, cs: any, tableConfig: TableConfig): string {
        const returnIdSql = tableConfig.createReturn ? ` RETURNING ${tableConfig.idName}` : '';
        return pgp.helpers.insert(data, cs) + returnIdSql;
    }

    updateSql(data: object, cs: any, tableConfig: TableConfig): string {
        return pgp.helpers.update(data, cs);
    }

    static geomProcess(content = [], geomPropertyName = 'geom'): void {
        content.forEach((value: any) => {
            const geom: string = value[geomPropertyName];
            if (geom) {
                const geomStr = geom.replace("POINT(", "").replace(")", "");
                const [lng, lat] = geomStr.split(' ').map(Number);
                value[geomPropertyName] = {lng, lat};
            }
        });
    }

    static geomSaveProcess(entity: any, geomPropertyName = 'geom', srid = `4326`): void {
        let lng, lat;
        const geom = entity[geomPropertyName];
        if (geom == null) {
            return;
        }

        try {
            ({lng, lat} = geom);
        } catch (e) {
            throw new ValidationError('geom 属性格式错误');
        }
        entity[geomPropertyName] = `SRID=${srid};POINT(${lng} ${lat})`;
    }

    columnProcess(s: string): string {
        return `"${s}"`;
    }
}